package com.google.code.stalkernet.example.ui;

import java.awt.BorderLayout;
import java.awt.Container;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableColumnModel;

import com.google.code.stalkernet.advertisements.Advertisements;
import com.google.code.stalkernet.async.factory.AsyncFactory;
import com.google.code.stalkernet.database.Database;
import com.google.code.stalkernet.example.ui.action.AddEntryAction;
import com.google.code.stalkernet.example.ui.action.AddNEntriesAction;
import com.google.code.stalkernet.example.ui.action.AddServerSocketAddressAction;
import com.google.code.stalkernet.example.ui.action.ConnectPeerAction;
import com.google.code.stalkernet.example.ui.action.DisconnectPeersAction;
import com.google.code.stalkernet.example.ui.action.RemoveServerSocketAddressesAction;
import com.google.code.stalkernet.node.Node;
import com.google.code.stalkernet.peer.Peer;
import com.google.code.stalkernet.reactor.Reactor;
import com.google.code.stalkernet.server.Server;

public class MainFrame extends JFrame
{
    private final static Charset UTF8 = Charset.forName ("UTF-8");

    public MainFrame (
        Reactor reactor, Database database, Server server,
        AsyncFactory <? super SocketAddress, ? extends Peer> peerFactory,
        Node node, Advertisements incomingAdvertisements,
        Advertisements outgoingAdvertisements)
    {
        super ("Stalker.Net [" + node.getID () + "]");

        if (database == null)
            throw new IllegalArgumentException ("Database is null");

        setDefaultCloseOperation (EXIT_ON_CLOSE);

        JButton addEntryButton = new JButton (
            new AddEntryAction (reactor, database));
        JButton addNEntriesButton = new JButton (
            new AddNEntriesAction (reactor, database));

        Box databaseToolbar = Box.createHorizontalBox ();
        databaseToolbar.add (addEntryButton);
        databaseToolbar.add (Box.createHorizontalStrut (5));
        databaseToolbar.add (addNEntriesButton);
        databaseToolbar.add (Box.createHorizontalGlue ());

        final DatabaseTableModel databaseTableModel =
            new DatabaseTableModel (reactor, database);

        JTable databaseTable = new JTable (databaseTableModel);
        databaseTable.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        databaseTable.setAutoCreateRowSorter (true);
        TableColumnModel databaseTableColumnModel = databaseTable.getColumnModel ();
        databaseTableColumnModel.getColumn (0).setPreferredWidth (280);
        databaseTableColumnModel.getColumn (1).setPreferredWidth (160);
        databaseTableColumnModel.getColumn (2).setPreferredWidth (1120);

        JScrollPane databaseScrollPane = new JScrollPane (
            databaseTable,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        final JLabel databaseStatusBar = new JLabel ();
        databaseTableModel.addTableModelListener (
            new TableModelListener()
        {
            {updateStatusBar ();}

            @Override
            public void tableChanged (TableModelEvent e)
            {
                updateStatusBar ();
            }

            private void updateStatusBar ()
            {
                databaseStatusBar.setText (
                    "Total entries: " + databaseTableModel.getRowCount ());
            }
        });

        JPanel databasePanel = new JPanel (new BorderLayout (8, 8));
        databasePanel.setBorder (BorderFactory.createEmptyBorder (8, 8, 8, 8));
        databasePanel.add (databaseToolbar, BorderLayout.NORTH);
        databasePanel.add (databaseScrollPane, BorderLayout.CENTER);
        databasePanel.add (databaseStatusBar, BorderLayout.SOUTH);

        ServerTableModel serverTableModel = new ServerTableModel (
            reactor, server);

        JTable serverTable = new JTable (serverTableModel);
        serverTable.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        serverTable.setAutoCreateRowSorter (true);
        TableColumnModel serverTableColumnModel = serverTable.getColumnModel ();
        serverTableColumnModel.getColumn (0).setPreferredWidth (160);
        serverTableColumnModel.getColumn (1).setPreferredWidth (80);

        JButton addServerSocketAddressButton =
            new JButton (new AddServerSocketAddressAction (server, reactor));

        JButton removeServerSocketAddressesButton =
            new JButton (new RemoveServerSocketAddressesAction (
                reactor,
                serverTable.getSelectionModel (), serverTableModel, server));

        Box serverToolbar = Box.createHorizontalBox ();
        serverToolbar.add (addServerSocketAddressButton);
        serverToolbar.add (Box.createHorizontalStrut (5));
        serverToolbar.add (removeServerSocketAddressesButton);
        serverToolbar.add (Box.createHorizontalGlue ());

        JScrollPane serverScrollPane = new JScrollPane (
            serverTable,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JPanel serverPanel = new JPanel (new BorderLayout (8, 8));
        serverPanel.setBorder (BorderFactory.createEmptyBorder (8, 8, 8, 8));
        serverPanel.add (serverToolbar, BorderLayout.NORTH);
        serverPanel.add (serverScrollPane, BorderLayout.CENTER);
        PeersTableModel peersTableModel = new PeersTableModel (reactor, node);

        JTable peersTable = new JTable (peersTableModel);
        peersTable.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        peersTable.setAutoCreateRowSorter (true);
        TableColumnModel peersTableColumnModel = peersTable.getColumnModel ();
        peersTableColumnModel.getColumn (0).setPreferredWidth (160);
        peersTableColumnModel.getColumn (1).setPreferredWidth (80);
        peersTableColumnModel.getColumn (2).setPreferredWidth (160);
        peersTableColumnModel.getColumn (3).setPreferredWidth (80);

        JButton connectPeerButton =
            new JButton (new ConnectPeerAction (
                peerFactory, node, reactor));

        JButton disconnectPeersButton =
            new JButton (new DisconnectPeersAction (
                reactor,
                peersTable.getSelectionModel (), peersTableModel));

        Box peersToolbar = Box.createHorizontalBox ();
        peersToolbar.add (connectPeerButton);
        peersToolbar.add (Box.createHorizontalStrut (5));
        peersToolbar.add (disconnectPeersButton);
        peersToolbar.add (Box.createHorizontalGlue ());

        JScrollPane peersScrollPane = new JScrollPane (
            peersTable,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JPanel peersPanel = new JPanel (new BorderLayout (8, 8));
        peersPanel.setBorder (BorderFactory.createEmptyBorder (8, 8, 8, 8));
        peersPanel.add (peersToolbar, BorderLayout.NORTH);
        peersPanel.add (peersScrollPane, BorderLayout.CENTER);

        AdvertisementsTableModel incomingAdvertisementsTableModel =
            new AdvertisementsTableModel (reactor, incomingAdvertisements);

        JTable incomingAdvertisementsTable = new JTable (
            incomingAdvertisementsTableModel);
        incomingAdvertisementsTable.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        incomingAdvertisementsTable.setAutoCreateRowSorter (true);
        TableColumnModel incomingAdvertisementsTableColumnModel =
            incomingAdvertisementsTable.getColumnModel ();
        incomingAdvertisementsTableColumnModel.getColumn (0).
            setPreferredWidth (160);
        incomingAdvertisementsTableColumnModel.getColumn (1).
            setPreferredWidth (80);
        incomingAdvertisementsTableColumnModel.getColumn (2).
            setPreferredWidth (160);

        Box incomingAdvertisementsToolbar = Box.createHorizontalBox ();
        incomingAdvertisementsToolbar.add (Box.createHorizontalGlue ());

        JScrollPane incomingAdvertisementsScrollPane = new JScrollPane (
            incomingAdvertisementsTable,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JPanel incomingAdvertisementsPanel = new JPanel (
            new BorderLayout (8, 8));
        incomingAdvertisementsPanel.setBorder (
            BorderFactory.createEmptyBorder (8, 8, 8, 8));
        incomingAdvertisementsPanel.add (
            incomingAdvertisementsToolbar, BorderLayout.NORTH);
        incomingAdvertisementsPanel.add (
            incomingAdvertisementsScrollPane, BorderLayout.CENTER);

        AdvertisementsTableModel outgoingAdvertisementsTableModel =
            new AdvertisementsTableModel (reactor, outgoingAdvertisements);

        JTable outgoingAdvertisementsTable = new JTable (
            outgoingAdvertisementsTableModel);
        outgoingAdvertisementsTable.setAutoResizeMode (JTable.AUTO_RESIZE_OFF);
        outgoingAdvertisementsTable.setAutoCreateRowSorter (true);
        TableColumnModel outgoingAdvertisementsTableColumnModel =
            outgoingAdvertisementsTable.getColumnModel ();
        outgoingAdvertisementsTableColumnModel.getColumn (0).
            setPreferredWidth (160);
        outgoingAdvertisementsTableColumnModel.getColumn (1).
            setPreferredWidth (80);
        outgoingAdvertisementsTableColumnModel.getColumn (2).
            setPreferredWidth (160);

        Box outgoingAdvertisementsToolbar = Box.createHorizontalBox ();
        outgoingAdvertisementsToolbar.add (Box.createHorizontalGlue ());

        JScrollPane outgoingAdvertisementsScrollPane = new JScrollPane (
            outgoingAdvertisementsTable,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JPanel outgoingAdvertisementsPanel = new JPanel (
            new BorderLayout (8, 8));
        outgoingAdvertisementsPanel.setBorder (
            BorderFactory.createEmptyBorder (8, 8, 8, 8));
        outgoingAdvertisementsPanel.add (
            outgoingAdvertisementsToolbar, BorderLayout.NORTH);
        outgoingAdvertisementsPanel.add (
            outgoingAdvertisementsScrollPane, BorderLayout.CENTER);

        JTabbedPane mainTabbedPane = new JTabbedPane ();
        mainTabbedPane.add ("Database", databasePanel);
        mainTabbedPane.add ("Server", serverPanel);
        mainTabbedPane.add ("Peers", peersPanel);
        mainTabbedPane.add ("Incoming Advertisements",
            incomingAdvertisementsPanel);
        mainTabbedPane.add ("Outgoing Advertisements",
            outgoingAdvertisementsPanel);

        final JTextArea logTextArea = new JTextArea (8, 80);
        logTextArea.setEditable (false);

        LogManager.getLogManager ().reset ();
        Logger.getLogger ("com.google.code.stalkernet").setLevel (Level.FINEST);
        Logger.getLogger ("com.google.code.stalkernet").addHandler (
            new TextAreaHandler (logTextArea));

        JScrollPane logScrollPane = new JScrollPane (
            logTextArea,
            JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
            JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        JPanel logPanel = new JPanel (new BorderLayout ());
        logPanel.add (logScrollPane);

        JPanel mainPanel = new JPanel (new BorderLayout (8, 8));
        mainPanel.setBorder (BorderFactory.createEmptyBorder (8, 8, 8, 8));
        mainPanel.add (mainTabbedPane, BorderLayout.CENTER);
        mainPanel.add (logPanel, BorderLayout.SOUTH);

        Container contentPanel = getContentPane ();
        contentPanel.setLayout (new BorderLayout ());
        getContentPane ().add (mainPanel, BorderLayout.CENTER);
    }

    private static class TextAreaHandler extends Handler implements Runnable
    {
        private final JTextArea textArea;
        private StringBuilder stringBuilder = null;

        boolean doneHeader = false;

        private TextAreaHandler (JTextArea textArea)
        {
            if (textArea == null)
                throw new IllegalArgumentException ("Text are is null");

            this.textArea = textArea;

            setFormatter (new SimpleFormatter ());
        }

        @Override
        public synchronized void publish (LogRecord record)
        {
            if (record == null)
                throw new IllegalArgumentException ("Record is null");

            Formatter formatter = getFormatter ();

            if (!doneHeader)
            {
                append (formatter.getHead (this));
                doneHeader = true;
            }

            append (formatter.format (record));

        }

        @Override
        public void flush ()
        {
            // Do nothing
        }

        @Override
        public synchronized void close () throws SecurityException
        {
            Formatter formatter = getFormatter ();

            if (!doneHeader)
            {
                append (formatter.getHead (this));
                doneHeader = true;
            }

            append (formatter.getTail (this));
        }

        @Override
        public synchronized void run ()
        {
            textArea.append (stringBuilder.toString ());
            stringBuilder = null;
        }

        private synchronized void append (String message)
        {
            if (!message.isEmpty ())
            {
                if (stringBuilder == null)
                {
                    stringBuilder = new StringBuilder ();
                    SwingUtilities.invokeLater (this);
                }

                stringBuilder.append (message);
            }
        }
    }
}
